#region Usings
using System;
using System.Collections.Generic;
using System.IO;
using System.Collections.Specialized;
using System.Text;
using System.Net;
using System.Web;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Collections;
using System.Threading;
using System.Diagnostics;
using Transferomatic.Enumerators;
using System.Windows.Forms;
#endregion

namespace Transferomatic
{
    public class TransferAgent
    {
        #region Members
        private Guid _ID = new Guid();
        private TransferAgentType _TransferType = TransferAgentType.File;
        private TransferAgentAction _Action = TransferAgentAction.Copy;
        private DirectoryInfo _Source;
        private List<FileInfo> _SourceFiles = new List<FileInfo>();
        private DirectoryInfo _Destination;
        private TransferAgentStatus _Status = TransferAgentStatus.NotStarted;
        private string _StatusMessage = String.Empty;
        private FileInfo _CurrentFile;
        private long _SourceSize;
        private long _TransferredAmount;
        private int _TransferBufferSize = 32768; // 32KB
        #endregion

        #region Properties
        public Guid ID
        {
            get { return this._ID; }
        }

        public TransferAgentType TransferType
        {
            get { return this._TransferType; }
        }

        public TransferAgentAction Action
        {
            get { return this._Action; }
        }

        public List<FileInfo> SourceFiles
        {
            get { return this._SourceFiles; }
            set
            {
                this._SourceFiles = value;

                foreach (FileInfo thisFileInfo in this._SourceFiles)
                {
                    this._SourceSize += thisFileInfo.Length;
                }
            }
        }

        public long SourceSize
        {
            get
            {
                long theTotalSize = 0;

                foreach (FileInfo thisFileInfo in this._SourceFiles)
                {
                    theTotalSize += thisFileInfo.Length;
                }

                return theTotalSize;
            }
        }

        public string SourceDescription
        {
            get
            {
                if (this.SourceFiles.Count.Equals(1))
                {
                    return this.SourceFiles[0].ToString();
                }
                else
                {
                    return String.Format(
                        CultureInfo.CurrentCulture,
                        "{0} files from {1}",
                        this._SourceFiles.Count,
                        this._Source.FullName);
                }
            }
        }

        public DirectoryInfo Destination
        {
            get { return this._Destination; }
        }

        public TransferAgentStatus Status
        {
            get { return this._Status; }
            set 
            { 
                this._Status = value;

                if (null != this.StatusChanged)
                {
                    this.StatusChanged(this, new EventArgs());
                }
            }
        }

        public string StatusMessage
        {
            get { return this._StatusMessage; }
            set { this._StatusMessage = value; }
        }

        public FileInfo CurrentFile
        {
            get { return this._CurrentFile; }
        }
        
        public int TransferredPercentage
        {
            get
            {
                return Convert.ToInt32(
                    ((decimal)this._TransferredAmount /
                        (decimal)this.SourceSize) * 100,
                    CultureInfo.CurrentCulture);
            }
        }

        public long TransferredAmount
        {
            get { return this._TransferredAmount; }
        }

        public decimal RemainingAmount
        {
            get 
            {
                decimal theRemainingAmount = Convert.ToDecimal(
                    this.SourceSize - this._TransferredAmount,
                    CultureInfo.CurrentCulture);

                return Math.Round(theRemainingAmount / 1048576, 2);
            }
        }
        #endregion

        #region Methods
        #region Constructor
        public TransferAgent(
            TransferAgentType theType,
            TransferAgentAction theAction,
            int theTransferBufferSize,
            DirectoryInfo theSource,
            DirectoryInfo theDestination)
        {
            this._TransferType = theType;
            this._Action = theAction;
            this._TransferBufferSize = theTransferBufferSize;
            this._Source = theSource;
            this._Destination = theDestination;
        }
        #endregion

        /// <summary>
        /// Starts the download of the attached url into the given directory.
        /// </summary>
        public void Start()
        {
            try
            {
                foreach (FileInfo thisSourceFileInfo in this._SourceFiles)
                {
                    this._CurrentFile = thisSourceFileInfo;

                    FileInfo thisDestinationFileInfo = new FileInfo(
                        thisSourceFileInfo.FullName.Replace(
                            this._Source.FullName, 
                            this._Destination.FullName));

                    using (FileStream theSourceFileStream = new FileStream(
                        thisSourceFileInfo.FullName,
                        FileMode.Open))
                    {
                        if (!thisDestinationFileInfo.Directory.Exists)
                        {
                            thisDestinationFileInfo.Directory.Create();
                        }
                        else if (thisDestinationFileInfo.Exists)
                        {
                            this.Status = TransferAgentStatus.Successful;
                            if (null != this.Completed)
                            {
                                this.Completed(this, new EventArgs());
                            }
                        }

                        using (FileStream theDestinationFileStream = new FileStream(
                            thisDestinationFileInfo.FullName,
                            FileMode.OpenOrCreate))
                        {
                            byte[] theBuffer = new byte[this._TransferBufferSize];
                            int theReadCount = theSourceFileStream.Read(theBuffer, 0, this._TransferBufferSize);

                            while (theReadCount > 0)
                            {
                                if (this.Status.Equals(TransferAgentStatus.Paused))
                                {
                                    if (null != this.Paused)
                                    {
                                        this.Paused(this, new EventArgs());
                                    }
                                    return;
                                }
                                else if (this.Status.Equals(TransferAgentStatus.Cancelled))
                                {
                                    if (null != this.Cancelled)
                                    {
                                        this.Cancelled(this, new EventArgs());
                                    }
                                    return;
                                }
                                else
                                {
                                    this.Status = TransferAgentStatus.Transferring;
                                    if (null != this)
                                    {
                                        this.Transferring(this, new EventArgs());
                                    }

                                    theDestinationFileStream.Write(theBuffer, 0, theReadCount);

                                    theReadCount = theSourceFileStream.Read(theBuffer, 0, this._TransferBufferSize);

                                    this._TransferredAmount += theReadCount;
                                }
                            }

                            theDestinationFileStream.Close();
                        }

                        theSourceFileStream.Close();
                    }

                    thisDestinationFileInfo.Attributes = 
                        thisSourceFileInfo.Attributes;

                    if (this._Action.Equals(TransferAgentAction.Move))
                    {
                        thisSourceFileInfo.Delete();
                    }
                }

                this.Status = TransferAgentStatus.Successful;
                if (null != this.Completed)
                {
                    this.Completed(this, new EventArgs());
                }
            }
            catch (Exception e)
            {
                this.Status = TransferAgentStatus.Failed;
                this.StatusMessage = e.Message;

                if (null != this.Failed)
                {
                    this.Failed(this, new EventArgs());
                }
            }
        }
        #endregion

        #region Events
        public event EventHandler Transferring;
        public event EventHandler Paused;
        public event EventHandler Completed;
        public event EventHandler Cancelled;
        public event EventHandler Failed;
        public event EventHandler StatusChanged;
        #endregion
    }
}
